package anonymous;

import java.util.List;
import java.util.ArrayList;
import java.util.Set;
import java.util.HashSet;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;

import rescuecore2.worldmodel.EntityID;
import rescuecore2.worldmodel.Entity;
import rescuecore2.worldmodel.ChangeSet;
import rescuecore2.messages.Command;
import rescuecore2.Constants;
import rescuecore2.log.Logger;

import rescuecore2.standard.components.StandardAgent;
import rescuecore2.standard.entities.*;
import rescuecore2.standard.kernel.comms.ChannelCommunicationModel;
import rescuecore2.standard.kernel.comms.StandardCommunicationModel;

import rescuecore2.misc.geometry.GeometryTools2D;
import rescuecore2.misc.geometry.Point2D;
import rescuecore2.misc.geometry.Line2D;

/**
  Abstract base class for sample agents.
  @param <E> The subclass of StandardEntity this agent wants to control.
  */
public abstract class AbstractAnonymousAgent<E extends StandardEntity> extends StandardAgent<E> {

	public enum State { STARTUP, PATROL, TASK, DISPLACEMENT };
	public State current_state;

	private static final int RANDOM_WALK_LENGTH = 50;
	private static final String SAY_COMMUNICATION_MODEL = StandardCommunicationModel.class.getName();
	private static final String SPEAK_COMMUNICATION_MODEL = ChannelCommunicationModel.class.getName();

	protected TaskList taskList;

	/**
	  The search algorithm.
	  */
	protected AnonymousSearch search;

	/**
	  Whether to use AKSpeak messages or not.
	  */
	protected boolean useSpeak;

	/**
	  Caches
	  */
	protected List<EntityID> buildingIDs;
	protected List<EntityID> roadIDs;
	protected List<EntityID> refugeIDs;

	private Map<EntityID, Set<EntityID>> neighbours;

	/**
	  Construct an AbstractAnonymousAgent.
	  */
	protected AbstractAnonymousAgent() {
		this.current_state = State.PATROL;
		this.taskList = new TaskList();
	}

	/**
	 * Functions to be overwriten on specific classes
	 * */
	protected boolean iAmFireBrigade(){
		return false;
	}

	protected boolean iAmPolice(){
		return false;
	}

	protected boolean iAmAmbulance(){
		return false;
	}

	@Override
		protected void postConnect() {
			super.postConnect();
			buildingIDs = new ArrayList<EntityID>();
			roadIDs = new ArrayList<EntityID>();
			refugeIDs = new ArrayList<EntityID>();
			for (StandardEntity next : model) {
				if (next instanceof Building) {
					buildingIDs.add(next.getID());
				}
				if (next instanceof Road) {
					roadIDs.add(next.getID());
				}
				if (next instanceof Refuge) {
					refugeIDs.add(next.getID());
				}
			}
			search = new AnonymousSearch(model);
			neighbours = search.getGraph();
			useSpeak = config.getValue(Constants.COMMUNICATION_MODEL_KEY).equals(SPEAK_COMMUNICATION_MODEL);
			Logger.debug("Communcation model: " + config.getValue(Constants.COMMUNICATION_MODEL_KEY));
			Logger.debug(useSpeak ? "Using speak model" : "Using say model");
		}

	protected void startup(int time){}
	protected void patrol(int time){}
	protected void task(int time){}
	protected void displacement(int time){}

	@Override
		protected void think(int time, ChangeSet changed, Collection<Command> heard) {
			Logger.debug("Valdinho -- Eu penso! ");
			Logger.debug("Valdinho -- estado " + this.current_state);
			hear(heard);
			sense(changed);
			switch(this.current_state){
				case STARTUP:
					startup(time);
					break;
				case PATROL:
					patrol(time);
					break;
				case TASK:
					task(time);
					break;
				case DISPLACEMENT:
					displacement(time);
					break;
			}
		}

	protected boolean hasTask(){
		return !taskList.isEmpty();
	}

	private void hear(Collection<Command> heard){
		//TODO: Implement
	}

	private void sense(ChangeSet changed){
		for (EntityID eid : changed.getChangedEntities()) {
			StandardEntity next = model.getEntity(eid);
			if (next instanceof Area){
				//buildings and roads extends Area and both
				//can be blocked
				if(((Area)next).isBlockadesDefined()){
					if(iAmPolice()){
						for( EntityID beid : ((Area)next).getBlockades()){
							Blockade blockade = (Blockade) model.getEntity(beid);
							Task t = new Task(blockade.getID(), next.getLocation(model));
							t.area = (Area)next;
							t.cost = blockade.getRepairCost();
							//t.cost = findDistanceTo(blockade, ((Human)me()).getX(), ((Human)me()).getY());
							//if (t.cost != 0)
							//	t.cost = 100000000 / t.cost;
							Logger.debug("TANNOS -- Adicionei Custo ="+t.cost);
							taskList.add(t);
						}
					}
					else{
						//tell the center
					}
				}

				//Buildings can also be on fire
				if (next instanceof Building) {
					buildingIDs.add(next.getID());
					if( ((Building)next).isOnFire() ){
						if(iAmFireBrigade()){
							Task t = new Task(eid, next.getLocation(model));
							t.fieryness = ((Building)next).getFieryness();
							t.brokenness = ((Building)next).getBrokenness();
							t.temperature = ((Building)next).getTemperature();
							t.importance = ((Building)next).getImportance();
							taskList.add(t);
							Logger.debug("TANNOS -- Adicionei Incendio");
						}
						else{
							//tell the center
						}
					}
				}

				//Road
				if (next instanceof Road) {
					roadIDs.add(next.getID());
				}
			}
			else if (next instanceof Refuge) {
				refugeIDs.add(next.getID());
			}
			else if(next instanceof Civilian){
				if( ((Civilian)next).getDamage() >= 0){ // GAMBIARRA PRA ACEITAR QUEM NÂO TÀ FERIDO PRA TESTES!
					if(iAmAmbulance()){
						Logger.debug("TANNOS -- Achei Ferido");
						Task t = new Task(eid, next.getLocation(model));
						t.hp = ((Civilian)next).getHP();
						t.damage = ((Civilian)next).getDamage();
						t.buriedness = ((Civilian)next).getBuriedness();
						taskList.add(t);
					}
					else{
						//tell the center
					}
				}
				else if( ((Civilian)next).getBuriedness() > 0){
					if(iAmAmbulance()){ // Removed Police from here
						//add to tasklist??
						Logger.debug("TANNOS -- Achei Enterrado");
						Task t = new Task(eid, next.getLocation(model));
						t.hp = ((Civilian)next).getHP();
						t.damage = ((Civilian)next).getDamage();
						t.buriedness = ((Civilian)next).getBuriedness();
						taskList.add(t);
					}
					else{
						//tell the center
					}
				}
			}
		}
	}

	
	/**
	  Construct a random walk starting from this agent's current location to a random building.
	  @return A random walk.
	  */
	protected List<EntityID> randomWalk() {
		List<EntityID> result = new ArrayList<EntityID>(RANDOM_WALK_LENGTH);
		Set<EntityID> seen = new HashSet<EntityID>();
		EntityID current = ((Human)me()).getPosition();
		for (int i = 0; i < RANDOM_WALK_LENGTH; ++i) {
			result.add(current);
			seen.add(current);
			List<EntityID> possible = new ArrayList<EntityID>(neighbours.get(current));
			Collections.shuffle(possible, random);
			boolean found = false;
			for (EntityID next : possible) {
				if (seen.contains(next)) {
					continue;
				}
				current = next;
				found = true;
				break;
			}
			if (!found) {
				// We reached a dead-end.
				break;
			}
		}
		return result;
	}

	private int findDistanceTo(Blockade b, int x, int y) {
		//        Logger.debug("Finding distance to " + b + " from " + x + ", " + y);
		List<Line2D> lines = GeometryTools2D.pointsToLines(GeometryTools2D.vertexArrayToPoints(b.getApexes()), true);
		double best = Double.MAX_VALUE;
		Point2D origin = new Point2D(x, y);
		for (Line2D next : lines) {
			Point2D closest = GeometryTools2D.getClosestPointOnSegment(next, origin);
			double d = GeometryTools2D.getDistance(origin, closest);
			//            Logger.debug("Next line: " + next + ", closest point: " + closest + ", distance: " + d);
			if (d < best) {
				best = d;
				//                Logger.debug("New best distance");
			}

		}
		return (int)best;
	}

	protected Collection<EntityID>  explore(){
		Set<EntityID> nodes = new HashSet<EntityID>();
		for( EntityID id : roadIDs ){
			for( EntityID n : ((Area)model.getEntity(id)).getNeighbours()){
				if(!roadIDs.contains(n)){
					nodes.add(n);
				}
			}
		}
		/*
		double min = 999999; //lots of nines
		EntityID choosen_one = null;
		Point2D myPoint = new Point2D(((Human)me()).getX(), ((Human)me()).getY());
		for( EntityID n : nodes ){
			int x = model.getEntity(n).getLocation(model).first();
			int y = model.getEntity(n).getLocation(model).second();
			Point2D n_point = new Point2D(x,y);
			double dist = GeometryTools2D.getDistance(myPoint,n_point);
			if(dist < min){
				min = dist;
				choosen_one = n;
			}
		}*/

		return nodes;
	}
}
